package vulnerability

import (
	"fmt"

	v1 "github.com/anchore/grype-db/pkg/db/v1"
	"github.com/anchore/grype/grype/cpe"
	"github.com/anchore/grype/grype/pkg"
	"github.com/anchore/syft/syft/distro"
	syftPkg "github.com/anchore/syft/syft/pkg"
	"github.com/facebookincubator/nvdtools/wfn"
)

type StoreAdapter struct {
	store v1.VulnerabilityStoreReader
}

func NewProviderFromStore(store v1.VulnerabilityStoreReader) *StoreAdapter {
	return &StoreAdapter{
		store: store,
	}
}

func (pr *StoreAdapter) GetByDistro(d distro.Distro, p pkg.Package) ([]*Vulnerability, error) {
	vulns := make([]*Vulnerability, 0)

	namespace := distroNamespace(d)
	allPkgVulns, err := pr.store.GetVulnerability(namespace, p.Name)
	if err != nil {
		return nil, fmt.Errorf("provider failed to fetch namespace='%s' pkg='%s': %w", namespace, p.Name, err)
	}

	for _, vuln := range allPkgVulns {
		vulnObj, err := NewVulnerability(vuln)
		if err != nil {
			return nil, fmt.Errorf("provider failed to parse distro='%s': %w", d, err)
		}

		vulns = append(vulns, vulnObj)
	}

	return vulns, nil
}

func (pr *StoreAdapter) GetByLanguage(l syftPkg.Language, p pkg.Package) ([]*Vulnerability, error) {
	vulns := make([]*Vulnerability, 0)

	namespaces := languageNamespaces(l)
	if namespaces == nil {
		return nil, fmt.Errorf("no store namespaces found for language '%s'", l)
	}

	for namespace, namer := range namespaces {
		for _, name := range namer(p) {
			allPkgVulns, err := pr.store.GetVulnerability(namespace, name)
			if err != nil {
				return nil, fmt.Errorf("provider failed to fetch namespace='%s' pkg='%s': %w", namespace, name, err)
			}

			for _, vuln := range allPkgVulns {
				vulnObj, err := NewVulnerability(vuln)
				if err != nil {
					return nil, fmt.Errorf("provider failed to parse language='%s': %w", l, err)
				}

				vulns = append(vulns, vulnObj)
			}
		}
	}

	return vulns, nil
}

func (pr *StoreAdapter) GetByCPE(requestCPE syftPkg.CPE) ([]*Vulnerability, error) {
	vulns := make([]*Vulnerability, 0)

	namespaces := cpeNamespaces()
	if namespaces == nil {
		return nil, fmt.Errorf("no store namespaces found for arbitrary CPEs")
	}

	if requestCPE.Product == wfn.Any || requestCPE.Product == wfn.NA {
		return nil, fmt.Errorf("product name is required")
	}

	for _, namespace := range namespaces {
		allPkgVulns, err := pr.store.GetVulnerability(namespace, requestCPE.Product)
		if err != nil {
			return nil, fmt.Errorf("provider failed to fetch namespace='%s' product='%s': %w", namespace, requestCPE.Product, err)
		}

		for _, vuln := range allPkgVulns {
			vulnCPEs, err := cpe.NewSlice(vuln.CPEs...)
			if err != nil {
				return nil, err
			}

			// compare the request CPE to the potential matches (excluding version, which is handled downstream)
			candidateMatchCpes := cpe.MatchWithoutVersion(requestCPE, vulnCPEs)

			if len(candidateMatchCpes) > 0 {
				vulnObj, err := NewVulnerability(vuln)
				if err != nil {
					return nil, fmt.Errorf("provider failed to parse cpe='%s': %w", requestCPE.BindToFmtString(), err)
				}

				vulnObj.CPEs = candidateMatchCpes

				vulns = append(vulns, vulnObj)
			}
		}
	}

	return vulns, nil
}
